import string
import os
import time
import types
import socket
import cPickle
import hashlib
import random

import jsonpickle

from bsddb3 import db

try:
	import json
except ImportError:
	import simplejson as json

import config
import constants
import client
import entities

try:
	import octopusdbm.client
	from octopusdbm.client import Dbm
	bOctopusImport = True
except ImportError:
	bOctopusImport = False

"""
    Copyright (C) 2009,2010  Jorge Alegre Vilches

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.    
"""

class ServiceCloud(client.Service):
	def __init__(self, sApp, sClassPath, *ClassArgsTuple, **ClassArgsDict):
		"""Service cloud, functionalities for cloud of server to process pieces of data and later on collecting
		results. In case only one server is desired then a single request or callMap() must be requested.
		@param sApp : String : Application, any string that can pack domains, subsystems, etc... Accepts any
		argument or keyword argument that will be passed to class constructor at server.
		@param sClassPath: String : Full class path"""
		if sApp == constants.APPLICATION_MAIN:
			self._HostName = socket.gethostname()
		else:
			if bOctopusImport:
				sHostName = self.getHostRandomFromCloud(sApp, constants.MAPPING_TYPE_PROC)
			else:
				self._HostName = socket.gethostname()
		if len(self._HostName) == 0:
			# Have SimpleSaasException or exception design
			raise Exception
		self._ClassPath = sClassPath
		self._RequestDict[u'ClassPath'] = sClassPath
		self._RequestDict[u'ClassArgsTuple'] = ClassArgsTuple
		self._RequestDict[u'ClassArgsDict'] = ClassArgsDict
	def callMap(self, sMethod, sDbMapHost, sWorkId, iOrder, *ArgsTuple, **ArgsDict):
		"""Requests processing and map result for method. Pieces will be saved in sDbMapHost with sWorkId and having
		order iOrder. When collecting the pieces will be put again together following the order they called the design
		pattern method. This method can be called directly defining an order. Takes additional arguments and keyword
		arguments the method may take.
		@param sMethod: String : Method
		@param sDbMapHost: String : Host name to use for OctopusDbm for writing data processed by servers.
		@param sWorkId: Integer : Identifier for this work, common for all pieces.
		@iOrder : Integer : Order to be used to latter collect pieces. Order is saved into database for collecting response."""
		bResponse = False
		self._RequestDict[u'Op'] = 'callMap'
		self._RequestDict[u'Method'] = sMethod
		self._RequestDict[u'ArgsTuple'] = ArgsTuple
		self._RequestDict[u'ArgsDict'] = ArgsDict
		self._RequestDict[u'WorkId'] = sWorkId
		self._RequestDict[u'Order'] = iOrder
		self._RequestDict[u'DbMapHost'] = sDbMapHost
		Dict = self._connect(bResponse)
	def collect(self, sWorkId, iNumberRequests):
		"""Collects response from pieces.
		@param sWorkId : Integer: Identifier for work, common to all pieces.
		@param iNumberRequests : Integer : Number of requests to process.
		@return: list : List : List of results."""
		bResponse = True
		self._RequestDict[u'Op'] = 'collect'
		self._RequestDict[u'WorkId'] = sWorkId
		self._RequestDict[u'NumberRequests'] = iNumberRequests
		Dict = self._connect(bResponse)
		if bResponse:
			if Dict[u'bResult'] == True:
				result = Dict[u'Result']
			else:
				if Dict.has_key(u'sError'):
					sErrMsg = Dict[u'sError']
					raise client.SimpleSaaSException('collect()', msg=sErrMsg)
		list = result
		return list
	def getHostRandomFromCloud(self, sApp, sMappingType):
		"""Get host from cloud randomly, either servers that process data or servers that hold databases used by
		the processors, defined in the sMappingType.
		@param sMappingType: Can be wither MAPPING_TYPE_PROC or MAPPING_TYPE_DB."""
		octopus = entities.Octopus()
		if sMappingType == constants.MAPPING_TYPE_PROC:
			MappingHostTuple = config.MappingHostTuple
		elif sMappingType == constants.MAPPING_TYPE_DB:
			MappingHostTuple = config.MappingDbHostTuple
		NotConnectedDict = {}
		while 1:
			sDbHostName = random.choice(MappingHostTuple)
			while NotConnectedDict.has_key(sDbHostName):
				sDbHostName = random.choice(MappingHostTuple)
			try:
				# Registry with down map servers. What if they are down, we have to connect to them????
				s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
				s.connect((sDbHostName, octopus.Port))
				s.close()
				oDbm = Dbm(sDbHostName, octopus.User, octopus.Password, octopus.Environment)
				if sMappingType == constants.MAPPING_TYPE_PROC:
					sTable = constants.TABLE_MAP_HOSTS
				elif sMappingType == constants.MAPPING_TYPE_DB:
					sTable = constants.TABLE_MAP_DB_HOSTS
				hostDataList = oDbm.search(sTable, [sApp], db.DB_BTREE, db.DB_CREATE)
				hostDataTuple = json.loads(random.choice(hostDataList)[1])
				sHostName, iStatus = hostDataTuple
				while iStatus != constants.STATUS_UP:
					hostDataTuple = json.loads(random.choice(hostDataList)[1])
					sHostName, iStatus = hostDataTuple						
				sHostName, iStatus = hostDataTuple
				self._HostName = sHostName
				break
			except socket.error:
				NotConnectedDict[sDbHostName] = ''
		return sHostName

class ServiceMap(object):
	"""Service map main class."""
	_WorkId = -1
	_App = ''
	_ClassPath = ''
	_ClassArgsTuple = ()
	_ClassArgsDict = {}
	def __init__(self, sApp, sClassPath, *ClassArgsTuple, **ClassArgsDict):
		"""Constructor. Accepts any argument or keyword argument
		@param sApp : String : Application
		@param sClassPath: String : Full class path, like module.package.Class or module.Class."""
		self._WorkId = self.getWorkId()
		self._App = sApp
		self._ClassPath = sClassPath
		self._ClassArgsTuple = ClassArgsTuple
		self._ClassArgsDict = ClassArgsDict
	def getWorkId(self):
		"""Get identifier for work, used by servers processing pieces and writing pieces in a BTREE index with
		duplicate keys being the WorkId.
		@return: sWorkId : String"""
		sWorkId = str(random.randint(1, constants.WORK_RANDOM_MAX)) + '-' + str(int(time.time()))
		return sWorkId

class PatternSubList(ServiceMap):
	"""Doc.	"""
	_oService = None
	_Host = ''
	_Order = 1
	_NumberRequests = 0
	def __init__(self, sApp, sClassPath, *ClassArgsTuple, **ClassArgsDict):
		"""Pattern using Map a request to a server and later on collecting all results from all servers processing
		requests. Uses DictList response. Accepts any argument or keyword argument that will be passed to constructor
		for the class to be instantiated.
		@param sApp: String : Application
		@param sClassPath: String : Full class path."""
		super(PatternSubList, self).__init__(sApp, sClassPath, *ClassArgsTuple, **ClassArgsDict)
		self._oService = None
		self._Host = ''
		self._Order = 1
	def request(self, sMethod, subList, *ArgsTuple, **ArgsDict):
		"""Request processing for subList for method, being a list of registers to be processed. 
		Accepts additional arguments and keyword arguments that the method may take. Method will process a
		dictionary which will be placed on database file for each request. Later on all pieces will be collected.
		@param sMethod : String: Method
		@param subList: List : list of registers to process, from 1 to n."""
		self._oService = ServiceCloud(self._App, self._ClassPath, *self._ClassArgsTuple, **self._ClassArgsDict)
		if self._Host == '':
			# First request after instantiating
			sHost = self._oService.getHostRandomFromCloud(self._App, constants.MAPPING_TYPE_DB)
			self._Host = sHost
			self._WorkId = self.getWorkId()
		self._oService.callMap(sMethod, self._Host, self._WorkId, self._Order, subList, *ArgsTuple, **ArgsDict)
		self._Order += 1
		self._NumberRequests += 1
	def collectAll(self):
		"""Collects all dictionaries from database file.
		@return: List : List of Dictionaries for all requests received."""
		list = self._oService.collect(self._WorkId, self._NumberRequests)
		return list
